package com.neihe.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.temp.SaTempUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.neihe.common.base.ApiException;
import com.neihe.common.base.ResultCode;
import com.neihe.common.constant.Const;
import com.neihe.common.constant.FileConst;
import com.neihe.common.enums.DelFlagEnum;
import com.neihe.common.enums.ShareDayTypeEnum;
import com.neihe.common.enums.ShareStatusEnum;
import com.neihe.entity.Share;
import com.neihe.entity.ShareFile;
import com.neihe.entity.User;
import com.neihe.entity.UserFile;
import com.neihe.entity.dto.file.QueryFileListContextDTO;
import com.neihe.entity.dto.share.CancelShareDTO;
import com.neihe.entity.dto.share.CheckShareCodeDTO;
import com.neihe.entity.dto.share.CreateShareUrlDTO;
import com.neihe.entity.dto.share.SaveShareFilesContextDTO;
import com.neihe.entity.vo.file.UserFileVO;
import com.neihe.entity.vo.share.*;
import com.neihe.mapper.ShareMapper;
import com.neihe.service.IShareFileService;
import com.neihe.service.IShareService;
import com.neihe.service.IUserFileService;
import com.neihe.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import static com.neihe.common.enums.ShareDayTypeEnum.getShareDescByCode;

@Slf4j
@Service
public class ShareServiceImpl extends ServiceImpl<ShareMapper, Share> implements IShareService {

    @Autowired
    private IShareFileService shareFileService;

    @Autowired
    private IUserService userService;

    @Autowired
    private IUserFileService userFileService;

    @Override
    public ShareUrlVO create(CreateShareUrlDTO requestParam) {
        Share share = new Share();
        share.setShareName(requestParam.getShareName());
        Integer shareDayType = requestParam.getShareDayType();
        share.setShareType(requestParam.getShareType());
        share.setShareDayType(requestParam.getShareDayType());

        Integer shareDay = ShareDayTypeEnum.getShareDayByCode(shareDayType);
        if (Objects.equals(Const.MINUS_ONE_INT, shareDay)) {
            throw new ApiException("分享天数非法");
        }

        share.setShareDay(shareDay);
        share.setShareEndTime(DateUtil.offsetDay(new Date(), shareDay));
        String shareUrl = createShareUrl();
        share.setShareUrl(shareUrl);
        int lastSlashIndex = shareUrl.lastIndexOf("/");
        String identifier = shareUrl.substring(lastSlashIndex + 1);
        share.setShareIdentifier(identifier);
        share.setShareCode(RandomUtil.randomString(6));
        share.setShareStatus(ShareStatusEnum.NORMAL.getCode());

        Long userId = StpUtil.getLoginIdAsLong();
        share.setUserId(userId);
        share.setCreateTime(new Date());

        if (!save(share)) {
            throw new ApiException("内部成为，保存分享信息失败");
        }

        saveShareFiles(share, requestParam.getShareFileIds(), userId);

        ShareUrlVO vo = new ShareUrlVO();
        vo.setShareUrl(share.getShareUrl());
        vo.setShareCode(share.getShareCode());
        vo.setPeriod(getShareDescByCode(shareDayType));
        return vo;
    }

    /**
     * 保存分享和分享文件的关联关系
     */
    private void saveShareFiles(Share record, List<Long> shareFileIds, Long userId) {
        SaveShareFilesContextDTO context = new SaveShareFilesContextDTO();
        context.setShareId(record.getShareId());
        context.setShareFileIds(shareFileIds);
        context.setUserId(userId);
        shareFileService.saveShareFiles(context);
    }

    @Value("${share.share-prefix}")
    private String sharePrefix;

    /**
     * 创建分享的URL
     */
    private String createShareUrl() {
        if (!sharePrefix.endsWith("/")) {
            sharePrefix += "/";
        }
        return sharePrefix + RandomUtil.randomString(6) + "-" + RandomUtil.randomString(3);
    }

    @Override
    public List<ShareUrlListVO> getShares() {
        List<Share> shareList = query().eq("user_id", StpUtil.getLoginIdAsLong()).list();
        return BeanUtil.copyToList(shareList, ShareUrlListVO.class);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelShare(CancelShareDTO requestParam) {
        List<Long> shareIds = requestParam.getShareIds();
        Long userId = StpUtil.getLoginIdAsLong();
        List<Share> records = listByIds(shareIds);

        if (CollUtil.isEmpty(records)) {
            throw new ApiException("无权限操作取消分享的动作");
        }

        for (Share record : records) {
            if (!Objects.equals(userId, record.getUserId())) {
                throw new ApiException("无权限操作取消分享的动作");
            }
        }

        if (!removeByIds(shareIds)) {
            throw new ApiException("取消分享失败");
        }

        QueryWrapper<ShareFile> queryWrapper = Wrappers.query();
        queryWrapper.in("share_id", shareIds);
        queryWrapper.eq("user_id", userId);
        if (!shareFileService.remove(queryWrapper)) {
            throw new ApiException("内部错误, 取消分享失败");
        }

    }

    @Override
    public ShareSimpleDetailVO simpleDetail(String shareIdentifier) {
        Share share = checkShareStatus(shareIdentifier);

        ShareSimpleDetailVO vo = new ShareSimpleDetailVO();
        vo.setShareId(share.getShareId());
        vo.setShareName(share.getShareName());
        vo.setShareTime(share.getCreateTime());

        User user = userService.getById(share.getUserId());
        if (user == null) {
            throw new ApiException("用户信息查询失败");
        }

        ShareUserInfoVO shareUserInfoVO = new ShareUserInfoVO();
        shareUserInfoVO.setUserId(share.getUserId());
        shareUserInfoVO.setEmail(encryptEmail(user.getEmail()));

        vo.setShareUserInfoVO(shareUserInfoVO);
        return vo;
    }


    @Override
    public String checkShareCode(CheckShareCodeDTO requestParam) {
        String identifier = requestParam.getIdentifier();
        Share share = checkShareStatus(identifier);
        if (!Objects.equals(share.getShareCode(), requestParam.getShareCode())) {
            throw new ApiException("提取码错误");
        }
        return SaTempUtil.createToken(identifier, 1800);
    }

    @Override
    public ShareDetailVO detail(String token) {
        // 校验分享的状态
        String identifier = SaTempUtil.parseToken(token, String.class);
        Share share = checkShareStatus(identifier);

        // 组成分享信息
        ShareDetailVO vo = new ShareDetailVO();
        vo.setShareId(share.getShareId());
        vo.setShareName(share.getShareName());
        vo.setCreateTime(share.getCreateTime());
        vo.setShareDay(share.getShareDay());
        vo.setShareEndTime(share.getShareEndTime());

        // 查询分享的主体信息
        List<Long> fileIds = getShareFileIds(share.getShareId());

        QueryFileListContextDTO context = new QueryFileListContextDTO();
        context.setUserId(share.getUserId());
        context.setDelFlag(DelFlagEnum.NO.getCode());
        context.setFileIds(fileIds);

        // 查询分享的文件列表
        List<UserFileVO> userFileVOList = userFileService.getFileList(context);
        vo.setUserFileVOList(userFileVOList);

        // 查询分享者的信息
        User user = userService.getById(share.getUserId());
        if (user == null) {
            throw new ApiException("用户信息查询失败");
        }

        ShareUserInfoVO shareUserInfoVO = new ShareUserInfoVO();
        shareUserInfoVO.setUserId(user.getId());
        shareUserInfoVO.setEmail(encryptEmail(user.getEmail()));

        vo.setShareUserInfoVO(shareUserInfoVO);
        return vo;
    }

    @Override
    public void refreshShareStatus(List<Long> allAvailableFileIdList) {
        List<Long> shareIds = getShareIdListByFileIdList(allAvailableFileIdList);
        if (CollUtil.isEmpty(shareIds)) {
            return;
        }
        Set<Long> shareIdSet = CollUtil.newHashSet(shareIds);
        shareIdSet.forEach(this::refreshOneShareStatus);
    }

    /**
     * 刷新一个分享的分享状态
     * <p>
     * - 查询对应的分享信息，判断有效
     * - 去判断该分享对应的文件以及所有的父文件信息均为正常，该种情况，把分享的状态变为正常
     * - 如果有分享的文件或者是父文件信息被删除，变更该分享的状态为有文件被删除
     */
    private void refreshOneShareStatus(Long shareId) {
        Share share = getById(shareId);
        if (share == null) {
            return;
        }

        ShareStatusEnum shareStatus = ShareStatusEnum.NORMAL;
        if (!checkShareFileAvailable(shareId)) {
            shareStatus = ShareStatusEnum.LAPSE;
        }

        if (Objects.equals(share.getShareStatus(), shareStatus.getCode())) {
            return;
        }

        // 刷新文件分享状态
        share.setShareStatus(shareStatus.getCode());
        if (!updateById(share)) {
            log.info("修改分享状态失败，分享ID为{}", share.getShareId());
        }
    }

    /**
     * 检查该分享所有的文件以及所有的父文件均为正常状态
     */
    private boolean checkShareFileAvailable(Long shareId) {
        List<Long> shareFileIdList = getShareFileIds(shareId);
        for (Long fileId : shareFileIdList) {
            if (!checkUpFileAvailable(fileId)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 检查该文件以及所有的文件夹信息均为正常状态
     */
    private boolean checkUpFileAvailable(Long fileId) {
        UserFile userFile = userFileService.getById(fileId);
        if (userFile == null) {
            return false;
        }

        if (Objects.equals(userFile.getDelFlag(), DelFlagEnum.YES.getCode())) {
            return false;
        }

        if (Objects.equals(userFile.getParentId(), FileConst.TOP_PARENT_ID)) {
            return true;
        }

        return checkUpFileAvailable(userFile.getParentId());
    }

    /**
     * 加密邮箱地址
     *
     * @param email 原始邮箱地址
     * @return 加密后的邮箱地址
     */
    private String encryptEmail(String email) {
        if (email == null || !email.contains("@")) {
            return email;
        }

        // 找到@符号的位置
        int atIndex = email.indexOf("@");

        // 如果前缀少于4个字符，保留全部前缀
        int prefixLength = Math.min(4, atIndex);

        // 构建加密后的邮箱
        StringBuilder sb = new StringBuilder();
        sb.append(email, 0, prefixLength); // 保留前缀
        sb.append("****"); // 加入星号
        sb.append(email.substring(atIndex)); // 添加@及后缀

        return sb.toString();
    }

    /**
     * 检查分享的状态是不是正常
     *
     * @param shareIdentifier 分享唯一标识
     * @return 结果
     */
    private Share checkShareStatus(String shareIdentifier) {
        Share share = query().eq("share_identifier", shareIdentifier).one();

        if (share == null) {
            throw new ApiException(ResultCode.SHARE_CANCELLED);
        }

        if (Objects.equals(ShareStatusEnum.LAPSE.getCode(), share.getShareStatus())) {
            throw new ApiException(ResultCode.SHARE_FILE_MISS);
        }

        if (Objects.equals(ShareDayTypeEnum.PERMANENT_VALIDITY.getCode(), share.getShareDayType())) {
            return share;
        }

        if (share.getShareEndTime().before(new Date())) {
            throw new ApiException(ResultCode.SHARE_EXPIRE);
        }

        return share;
    }

    /**
     * 查询分享对应的文件ID集合
     *
     * @param shareId 分享ID
     * @return 结果
     */
    private List<Long> getShareFileIds(Long shareId) {
        if (shareId == null) {
            return CollUtil.newArrayList();
        }
        QueryWrapper<ShareFile> queryWrapper = Wrappers.query();
        queryWrapper.select("file_id");
        queryWrapper.eq("share_id", shareId);
        return shareFileService.listObjs(queryWrapper, value -> (Long) value);
    }

    /**
     * 通过文件ID查询对应的分享ID集合
     */
    private List<Long> getShareIdListByFileIdList(List<Long> allAvailableFileIdList) {
        QueryWrapper<ShareFile> queryWrapper = Wrappers.query();
        queryWrapper.select("share_id");
        queryWrapper.in("file_id", allAvailableFileIdList);
        return shareFileService.listObjs(queryWrapper, value -> (Long) value);
    }
}
